#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

#include "Message.hpp"

unsigned int Message::getFilesize(const std::string &Filename) {
	struct stat Stat;
	if (stat(Filename.c_str(), &Stat) != 0) return 0;
	return Stat.st_size;
}

Message::Message() {
	data = NULL;
	length = 0;
	paddedLength = 0;
	blockSize = 16;
}

Message::Message(unsigned int BlockSize) {
	data = NULL;
	length = 0;
	paddedLength = 0;
	this->blockSize = BlockSize;
}

void Message::clear() {
	if (data) free(data);
	data = NULL;
	length = 0;
	paddedLength = 0;
}

bool Message::prepare(unsigned int size) {
	clear();

	length = size;
	paddedLength = size;
	if ((paddedLength % blockSize) != 0) {
		paddedLength += blockSize - (size % blockSize);
	}
	data = (unsigned char*)calloc(1, paddedLength);
	if (data == NULL) return false;
	
	return true;
}

void Message::readFromFile(const std::string &filename) {
	unsigned int Size = getFilesize(filename);
	if (Size == 0) return;

	FILE *f;
	f = fopen(filename.c_str(), "r");
	if (f == NULL) return;

	if (!prepare(Size)) return;

	unsigned int Remaining = Size;
	unsigned int Position = 0;
	while (Remaining > 0) {
		size_t Read;
		Read = fread(data + Position, 1, Remaining > 1024 ? 1024 : Remaining, f);
		if (Read > 0) {
			Remaining -= Read;
			Position += Read;
		} else {
			return;
		}
	}

	fclose(f);
}

void Message::writeToFile(const std::string &filename) const {
	FILE *f;
	f = fopen(filename.c_str(), "w");
	if (f == NULL) return;

	unsigned int Remaining = length;
	unsigned int Position = 0;
	while (Remaining > 0) {
		size_t Written;
		Written = fwrite(data + Position, 1, Remaining > 1024 ? 1024 : Remaining, f);
		if (Written > 0) {
			Remaining -= Written;
			Position += Written;
		} else {
			return;
		}
	}
	fclose(f);
}

unsigned int Message::getSize() const {
	return length;
}

unsigned int Message::getPaddedSize() const {
	return paddedLength;
}

unsigned int Message::getBlockCount() const {
	return paddedLength / blockSize;
}

Block Message::getBlock(unsigned int number) const {
	return Block(data + (blockSize * number), blockSize);
}

void Message::setBlock(unsigned int number, const Block &content) {
	if (content.getLength() > blockSize) {
		bool TrailZero = true;
		for (unsigned int i = blockSize; i < content.getLength(); i++) {
			if (content.getRO()[i] != 0) {
				TrailZero = false;
				break;
			}
		}
		if (TrailZero) {
			memcpy(data + (blockSize * number), content.getRO(), blockSize);
		} else {
			GENERICSTRMEXCEPTION("Cannot set non-zero block with given blocksize greater than " << blockSize << " (was " << content.getLength() << ")");
		}
		return;
	}
	memcpy(data + (blockSize * number), content.getRO(), content.getLength());
}

Message::~Message() {
	if (data) free(data);
}

